<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

namespace model\database;

/**
 * Description of Query
 *
 * @author Damian
 */
class Query extends \library\Base {

    /**
     * oznacza czy baza ma cytować wartości
     * @var bool
     */
    public $quote = true;

    /**
     *
     * @readwrite
     */
    public $joinedTables = array();

    /**
     * @readwrite
     * @var PDOConnector
     */
    protected $connector;

    /**
     * @read
     */
    protected $from;

    /**
     * @read
     */
    protected $fields;

    /**
     * @read
     */
    protected $limit;

    /**
     * @read
     */
    protected $offset;

    /**
     * @read
     */
    protected $order;

    /**
     * @read
     */
    protected $direction;

    /**
     * @read
     */
    protected $join = array();

    /**
     * @read
     */
    protected $where = array();

    protected function _quote($value) {
        $this->connector->connect();
        if (is_string($value)) {
            $escaped = $this->connector->escape($value);
            return "{$escaped}";
        }
        if (is_array($value)) {
            $buffer = array();
            foreach ($value as $i) {
                array_push($buffer, $this->_quote($i));
            }
            $bufferString = implode(", ", $buffer);
            return "({$bufferString})";
        }
        if (is_null($value)) {
            return "NULL";
        }
        if (is_bool($value)) {
            return (int) $value;
        }
        $this->connector->connect();
        return $this->connector->escape($value);
    }

    public function getSelectQuery() {
        return $this->_buildSelect();
    }

    public function from($from, $fields = array("*")) {
        if (empty($from)) {
            throw new \library\exceptions\model\ExceptionWrongQueryArgument();
        }
        $this->joinedTables[] = $from;
        $this->from = "`{$from}`";
        if ($fields) {
            $this->fields[$from] = $fields;
        }
        return $this;
    }
    /**
     * 
     * @param type $join <p>nazwa tabeli która zostanie dołączona</p>
     * @param type $on <p> nazwy kolumn na których jest dokonane złączenie</p>
     * @param type $joinType <p> typ złączenia</p>
     * @param type $fields [optional] <p> lista pól z nowej tabeli</p>
     * @return \model\database\Query
     * @throws \model\exceptions\ExceptionWrongQueryArgument
     * @throws \library\exceptions\model\ExceptionWrongQueryArgument
     */
    public function join($join, $on, $joinType = "INNER JOIN", $fields = array()) {
        $joins = ["INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN"];
        $this->joinedTables[] = $join;
        if (!in_array($joinType, $joins)) {
            throw new \model\exceptions\ExceptionWrongQueryArgument("zły typ złączenia: {$joinType}");
        }
        if (empty($join)) {
            throw new \model\exceptions\ExceptionWrongQueryArgument("pusta tabela");
        }
        if (empty($on)) {
            throw new \library\exceptions\model\ExceptionWrongQueryArgument("pusta lista pól złączenia");
        }
        $this->fields+=array($join => $fields);
        $this->join[] = " {$joinType} `{$join}` ON {$on}";
        return $this;
    }

    public function setLimit($limit, $page = 1) {
        if (empty($limit)) {
            throw new \library\exceptions\model\ExceptionWrongQueryArgument();
        }
        $this->limit = $limit;
        $this->offset = (int) $limit * ((int) $page - 1);
        return $this;
    }

    public function order($order, $direction = "asc") {
        if (empty($order)) {
            throw new \library\exceptions\model\ExceptionWrongQueryArgument();
        }
        $this->order = $order;
        $this->direction = $direction;
    }

    public function where() {
        $arguments = func_get_args();
        if (sizeof($arguments) < 1) {
            throw new \library\exceptions\model\ExceptionWrongQueryArgument();
        }
        $arguments[0] = preg_replace("#\?#", "%s", $arguments[0]);
        foreach (array_slice($arguments, 1, null, true) as $i => $parameter) {
            if ($this->quote) {
                if ($arguments[$i]!==" and " && $arguments[$i]!==" or " && $arguments[$i]!==" ") {
                    $arguments[$i] = $this->_quote($arguments[$i]);
                }
            }
        }
        $this->where[] = call_user_func_array("sprintf", $arguments);
        return $this;
    }

    protected function _buildSelect() {
        $where = $order = $limit = $join = "";
        $template = "Select %s from %s %s %s %s %s";
        $fields = array();
        $result = array();
        foreach ($this->fields as $_fields) {
            $where = $order = $limit = $join = "";
            foreach ($_fields as $field => $alias) {
                if (is_string($field)) {
                    $fields[] = "{$field} as {$alias}";
                } else {
                    $fields[] = $alias;
                }
            }
        }
        $fieldsList = join(", ", $fields);
        $_join = $this->join;
        if (!empty($_join)) {
            $join = implode(" ", $_join);
        }
        $_where = $this->where;
        if (!empty($_where)) {
            if (is_array($_where)) {
                $joined = implode(" ", $_where);
            } else {
                $joined = $_where;
            }
            $where = "where {$joined}";
        }
        $_order = $this->order;
        if (!empty($_order)) {
            $_direction = $this->direction;
            $order = "order by {$_order} {$_direction}";
        }
        $_limit = $this->limit;
        if (!empty($_limit)) {

            $_offset = $this->offset;
            if ($_offset) {
                $limit = "limit {$_offset}, {$_limit}";
            } else {
                $limit = "limit {$_limit}";
            }
        }
        $result = sprintf($template, $fieldsList, $this->from, $join, $where, $order, $limit);
        return $result;
    }

    protected function _buildInsert($data) {
        $template = "insert into %s (`%s`) values (%s)";
        foreach ($data as $field => $value) {
            $fields[] = $field;
            $values[] = $this->_quote($value);
        }
        $fields = implode("`, `", $fields);
        $values = implode(", ", $values);
        return sprintf($template, $this->from, $fields, $values);
    }

    protected function _buildUpdate($data) {
        $where = $limit = "";
        $template = "update %s set %s %s %s";
        foreach ($data as $field => $value) {
            $parts[] = "{$field} = " . $this->_quote($value);
        }
        $parts = implode(", ", $parts);

        $_where = $this->where;
        if (!empty($_where)) {
            $joined = implode(", ", $_where);
            $where = "where {$joined}";
        }
        $_limit = $this->limit;
        if (!empty($_limit)) {
            $_offset = $this->offset;
            $limit = "limit {$_limit} {$_offset}";
        }
        return sprintf($template, $this->from, $parts, $where, $limit);
    }
    public function getDeletedQuery(){
        return $this->_buildDelete();
    }

    protected function _buildDelete() {
        $where = $limit = "";
        $template = "delete from %s %s %s";
        $_where = $this->where;
        if (!empty($_where)) {
            $joined = implode(" ", $_where);
            $where = "where {$joined}";
        }
        $_limit = $this->limit;
        if (!empty($_limit)) {
            $_offset = $this->offset;
            $limit = "limit {$_limit} {$_offset}";
        }
        return sprintf($template, $this->from, $where, $limit);
    }

    /**
     * 
     * @param type $data
     * @return int <p>ostatnie zapisane id lub zero w przypadku aktualizacji</p>
     */
    public function save($data) {
        $isInsert = sizeof($this->where) == 0;
        if ($isInsert) {
            $sql = $this->_buildInsert($data);
        } else {
            $sql = $this->_buildUpdate($data);
        }
        $this->connector->executeSQL($sql);
        if ($isInsert) {
            return $this->connector->getLastInsertId();
        }
        return 0;
    }

    public function delete() {
        $sql = $this->_buildDelete();
        $result = $this->connector->executeSQL($sql);
        return $this->connector->getAffectedRows($result);
    }

    public function all($fetchStyle = Connector::FETCH_OBJECT, $className = "stdClass") {
        $result = array();
        $sql = $this->_buildSelect();
        $this->connector->connect();
        $resultQuery = $this->connector->executeSQL($sql)->fetchAll(\PDO::FETCH_ASSOC);
        $num_rows = sizeof($resultQuery);
        for ($j = 0; $j < $num_rows; $j++) {
            $temp=array();
            $i = 0;
            $row = $resultQuery[$j];
            foreach ($row as $key => $value) {
                if(strcmp("rows", $key)==0){
                    return $row;
                }
                if (strcmp(str_replace($this->joinedTables[$i] . "_", "", $key), $key) != 0) {
                    $keya = str_replace($this->joinedTables[$i] . "_", "", $key);
                    $temp[$this->joinedTables[$i]][$keya] = $value;
                } else {
                    $i++;
                    if (isset($this->joinedTables[$i])) {
                        $keya = str_replace($this->joinedTables[$i] . "_", "",  $key);
                        $temp[$this->joinedTables[$i]][$keya] = $value;
                    }
                    else {
                        $temp=$row;
                    }
                }
            }
            $result[] = $temp;
        }        

        $this->connector->disconnect();
        return $result;
    }

    public function first($fetchStyle = Connector::FETCH_OBJECT, $className = "stdClass") {
        $limit = $this->limit;
        $offset = $this->offset;
        $this->setLimit(1);
        $all = $this->all($fetchStyle, $className);
        if (strcmp(key($all), "rows")==0){
            return $all;
        }
        $first = \library\ArrayMethods::first($all[$this->joinedTables[0]]);
        $this->limit = $limit;
        $this->offset = $offset;
        return $first;
    }

    public function count() {

        $limit = $this->limit;
        $offset = $this->offset;
        $fields = $this->fields;
        $this->fields = array($this->from => array("count(*)" => "rows"));
        $this->setLimit(1);
        $row = $this->first();
        $this->fields = $fields;
        $this->limit = $limit;
        $this->offset = $offset;
        return $row["rows"];
    }

}
